/**
 * @(#)JotsFunctions.java	1.0 2000/12/14
 *
 * Copyright 2000 Thomas Barnekow. All Rights Reserved.
 *
 * This software is the proprietary information of Thomas Barnekow.
 * Use is subject to license terms.
 */

package jessx;

import jess.*;
import tb.jots.*;

/**
 * This is a {@link jess.Userpackage} integrating the <b>Java Object Transaction Service</b>
 * (JOTS, see {@link tb.jots}) into the Jess language. It defines the following functions:
 * <UL>
 * <LI><b><i>(jots-current): </i></b>return the calling thread's current transaction, i.e.,
 * an instance of {@link tb.jots.Current}.
 * <LI><b><i>(jots-current-begin): </i></b>begin a new transaction, which becomes the calling
 * thread's current transaction.
 * <LI><b><i>(jots-current-commit): </i></b>commit the calling thread's current transaction.
 * <LI><b><i>(jots-current-rollback): </i></b>rollback the calling thread's current transaction.
 * </UL>
 * The JotsFunctions {@link jess.Userpackage} also defines a set of {@link TransactionalUserfunction}s
 * wrapping some Jess intrinsics:
 * <ul>
 * <li><b><i>(assert-tx &lt;fact&gt;+): </i></b>transactional assert function
 * <li><b><i>(retract-tx &lt;integer-expression&gt;+): </i></b>transactional retract function
 * <li><b><i>(reset-tx): </i></b>transactional reset function
 * <li><b><i>(printout-tx &lt;router-identifier&gt; &lt;expression&gt;*): </i></b>transactional printout function
 * </ul>
 * The transactional functions will execute their delegate (assert, retract, etc.)
 * when (jots-current-commit) is called. The delegates will not be executed when
 * the transaction is rolled back.
 *
 * @author Thomas Barnekow
 * @version 1.0
 */
public class JotsFunctions implements Userpackage {

  /**
   * Function name: "jots-current"
   */
	public static final String CURRENT = "jots-current";

  /**
   * Function name: "jots-current-begin"
   */
	public static final String CURRENT_BEGIN = "jots-current-begin";

  /**
   * Function name: "jots-current-commit"
   */
	public static final String CURRENT_COMMIT = "jots-current-commit";

  /**
   * Function name: "jots-current-rollback"
   */
	public static final String CURRENT_ROLLBACK = "jots-current-rollback";

  /**
   * Function name: "jots-current-suspend"
   */
	public static final String CURRENT_SUSPEND = "jots-current-suspend";

  /**
   * Function name: "jots-current-resume"
   */
	public static final String CURRENT_RESUME = "jots-current-resume";

  /**
   * Add functions
   */
	public void add(Rete engine) {
		engine.addUserfunction(new JotsFunction(CURRENT));
		engine.addUserfunction(new JotsFunction(CURRENT_BEGIN));
		engine.addUserfunction(new JotsFunction(CURRENT_COMMIT));
		engine.addUserfunction(new JotsFunction(CURRENT_ROLLBACK));
		engine.addUserfunction(new JotsFunction(CURRENT_SUSPEND));
		engine.addUserfunction(new JotsFunction(CURRENT_RESUME));

    engine.addUserfunction(new TransactionalUserfunction("assert-tx", "assert"));
    engine.addUserfunction(new TransactionalUserfunction("retract-tx", "retract"));
    engine.addUserfunction(new TransactionalUserfunction("printout-tx", "printout"));
    engine.addUserfunction(new TransactionalUserfunction("reset-tx", "reset"));
	}

  class JotsFunction implements Userfunction {

    String m_name;

    public JotsFunction(String name) {
      m_name = name;
    }

    public String getName() {
      return m_name;
    }

    public Value call(ValueVector vv, Context context) throws JessException {
      try {
        if (m_name.equals(JotsFunctions.CURRENT)) {
          return new Value(OTS.getCurrent());
        }
        else if (m_name.equals(JotsFunctions.CURRENT_BEGIN)) {
          OTS.getCurrent().begin();
          return Funcall.TRUE;
        }
        else if (m_name.equals(JotsFunctions.CURRENT_COMMIT)) {
          OTS.getCurrent().commit(true);
          return Funcall.TRUE;
        }
        else if (m_name.equals(JotsFunctions.CURRENT_ROLLBACK)) {
          OTS.getCurrent().rollback();
          return Funcall.TRUE;
        }
        else if (m_name.equals(JotsFunctions.CURRENT_SUSPEND)) {
          Control control = OTS.getCurrent().suspend();
          if (control != null)
            return new Value(control);
          else
            return Funcall.NIL;
        }
        else if (m_name.equals(JotsFunctions.CURRENT_RESUME)) {
          if (vv.size() < 2) {
            throw new JessException(
              "JotsFunction.call", "Usage:", "(" + m_name + " <control-ref>)");
          }

          Value control = vv.get(1).resolveValue(context);
          switch (control.type()) {
          case RU.ATOM:
            if (control.equals(Funcall.NIL)) {
              OTS.getCurrent().resume(null);
              return Funcall.TRUE;
            } else {
              throw new JessException(
                "JotsFunction.call", "Illegal atom:", control.atomValue(context));
            }
          case RU.EXTERNAL_ADDRESS:
            OTS.getCurrent().resume((Control) control.externalAddressValue(context));
            return Funcall.TRUE;
          default:
            throw new JessException(
              "JotsFunction.call", "Illegal argument:", control.toString());
          }
        }
      } catch (JotsException ex) {
        throw new JessException(
          "JotsFunction.call", "Current threw an exception", ex);
      }

      // Who the heck are we?
      throw new JessException(
        "JotsFunction.call", "Unknown JOTS function name:", m_name);
    }
  }

  /**
   * A transactional userfunction.
   *
   * @author Thomas Barnekow
   * @version 1.0
   */
  public static class TransactionalUserfunction implements Userfunction {

    /**
     * Transactional function name.
     */
    protected String m_name;

    /**
     * Real function name.
     */
    protected String m_realName;

    /**
     * Expand {@link jess.ValueVector}s?
     */
    protected boolean m_expand;

    /**
     * Create a transactional {@link jess.Userfunction}.
     */
    public TransactionalUserfunction(String name, String realName) {
      this(name, realName, true);
    }

    /**
     * Create a transactional {@link jess.Userfunction}.
     */
    public TransactionalUserfunction(String name, String realName, boolean expand) {
      m_name = name;
      m_realName = realName;
      m_expand = expand;
    }

    public String getName() {
      return m_name;
    }

    public Value call(ValueVector vvec, Context context) throws JessException {
      // Create a Funcall that is to be executed upon commit
      Funcall fc = createFuncall(vvec, context, m_expand);

      // Make sure a transaction has begun and register the Resource with
      // the Coordinator
      try {
   		  new TransactionalFunctionCall(fc, context).requireTransaction();
      } catch (JotsException ex) {
        throw new JessException(
          "TransactionalUserfunction.call", "Resource threw an exception", ex);
      }

      // That's it for now. The rest happens when the transaction is committed.
      return Funcall.TRUE;
    }

    /**
     * Create a {@link jess.Funcall} for later execution.
     */
    protected Funcall createFuncall(ValueVector vvec, Context context, boolean expand)
      throws JessException
    {
      Funcall fc = new Funcall(m_realName, context.getEngine());
      AdviceFunctions.appendList(fc, vvec, expand, 1, context);
      return fc;
    }

    /**
     * Commit, i.e., execute, a {@link jess.Funcall}. Override this method in case
     * you want to alter, for instance, the exceptions thrown in case anything goes
     * wrong. This method only throws the {@link HeuristicHazardException}
     * signaling that something went wrong but nobody can tell what part exactly
     * had been executed or not. Throwing, for instance, a {@link HeuristicRollbackException}
     * would be more specific.
     *
     * @param fc      the Funcall to be executed
     * @param context the original context
     * @exception     HeuristicHazardException  in case anything goes wrong
     */
    protected void commitFuncall(Funcall fc, Context context)
      throws HeuristicRollbackException,
             HeuristicMixedException,
             HeuristicHazardException
    {
      try {
        fc.execute(context.getEngine().getGlobalContext());
      } catch (JessException ex) {
        throw new HeuristicHazardException("Could not commit Funcall:", ex);
      }
    }

    /**
     * Resource
     */
    protected class TransactionalFunctionCall implements Resource {

      protected Funcall m_fc;
      protected Context m_context;

    	/**
    	 * Delegate supporting the implementation of {@link tb.jots.Resource}s.
  	   */
    	protected ResourceSupport m_resourceSupport = new ResourceSupport(this);

      /**
       * Create a transactional {@link jess.Funcall}.
       *
       * @param fc      the Funcall to be executed
       * @param context the original context
       */
      protected TransactionalFunctionCall(Funcall fc, Context context) {
        m_fc = fc;
        m_context = context;
      }

      public String toString() {
        try {
          return "[TransactionalFunctionCall: " + AdviceFunctions.makeString(m_fc, m_context) + "]";
        } catch (JessException ex) {
          return "[TransactionalFunctionCall: " + ex.toString() + "]";
        }
      }

      protected void requireTransaction() throws JotsException {
        m_resourceSupport.requireTransaction();
      }

      /*
       * Resource operations
       */

      public int prepare() {
        return OTS.VOTE_COMMIT;
      }

      public void rollback()
        throws HeuristicCommitException,
               HeuristicMixedException,
               HeuristicHazardException
      {
        forget();
      }

      public void commit()
        throws NotPreparedException,
               HeuristicRollbackException,
               HeuristicMixedException,
               HeuristicHazardException
      {
        commitFuncall(m_fc, m_context);
      }

      public void commitOnePhase()
        throws TransactionRolledBackException,
               HeuristicHazardException
      {
        try {
          commitFuncall(m_fc, m_context);
        } catch (HeuristicRollbackException ex) {
          throw new TransactionRolledBackException("Heuristic decision made:", ex);
        } catch (HeuristicMixedException ex) {
          throw new HeuristicHazardException("Heuristic decision made:", ex);
        }
      }

      public void forget() {
        m_fc = null;
        m_context = null;
      }
    }
  }
}